using System;
using System.Collections.Generic;
using System.IO;
using System.Runtime.CompilerServices;
using Pinwheel.Toolkits;

namespace Pinwheel.LSerialize
{
    public unsafe partial struct LStreamWriter
    {
        [MethodImpl(MethodImplOptions.AggressiveInlining)]
        public bool WriteInstId<T>(LSerializeContext* context, ref T value, out long instId) where T : IEquatable<T>
        {
            bool needRef = false;
            instId = 0L;
            if (null! != value && !value.Equals(default))
            {
                int hash = value.GetHashCode();
                needRef = !LSerializeExtension.Cache.TryGetReference(ref hash, out instId);
                if (needRef)
                {
                    instId = context->IncreaseRefId();
                    LSerializeExtension.Cache.CacheReference(ref hash, ref instId);
                }
                AssistValHeader(new ValHeader() {Depth = context->Depth, Tag = context->Tag});
                AssistWriteUnmanaged((ulong)instId); 
                // Log.Info($"write [{context->Tag}] buffer span [{value}] at [{instId}].");
            }
            context->IncreaseTag();
            return needRef;
        }
        
        [MethodImpl(MethodImplOptions.AggressiveInlining)]
        public bool WriteInstId<T>(LSerializeContext* context, ref T? value, out long instId) where T : struct
        {
            bool needRef = false;
            instId = 0L;
            if (value.HasValue)
            {
                int hash = value.GetHashCode();
                needRef = !LSerializeExtension.Cache.TryGetReference(ref hash, out instId);
                if (needRef)
                {
                    instId = context->IncreaseRefId();
                    LSerializeExtension.Cache.CacheReference(ref hash, ref instId);
                }
                AssistValHeader(new ValHeader() {Depth = context->Depth, Tag = context->Tag});
                AssistWriteUnmanaged((ulong)instId); 
                // Log.Info($"write [{context->Tag}] buffer span [{value}] at [{instId}].");
            }
            context->IncreaseTag();
            return needRef;
        }
        
        [MethodImpl(MethodImplOptions.AggressiveInlining)]
        public bool WriteInstIdReference<T>(LSerializeContext* context, ref T value, out long instId)
        {
            bool needRef = false;
            instId = 0L;
            if (null != value)
            {
                int hash = value.GetHashCode();
                needRef = !LSerializeExtension.Cache.TryGetReference(ref hash, out instId);
                if (needRef)
                {
                    instId = context->IncreaseRefId();
                    LSerializeExtension.Cache.CacheReference(ref hash, ref instId);
                }
                AssistValHeader(new ValHeader() {Depth = context->Depth, Tag = context->Tag});
                AssistWriteUnmanaged((ulong)instId); 
            }
            context->IncreaseTag();
            return needRef;
        }
        
        [MethodImpl(MethodImplOptions.AggressiveInlining)]
        public void WriteUnmanagedStruct<T>(LSerializeContext* context, ref T value) where T : IEquatable<T>
        {
            if (WriteInstId(context, ref value, out var instId))
            {
                HandleUnmanagedStruct(out RefHeader* header);
                int size = Unsafe.SizeOf<T>();
                context->IncreaseDepth();
                // void* ptr = Unsafe.AsPointer(ref value);
                fixed (void* ptr = &value) WriteUnmanagedValue((byte*)ptr, size);
                context->DecreaseDepth();
                header->InstId = (int)instId;
                header->Size = size;
            }
        }
        
        [MethodImpl(MethodImplOptions.AggressiveInlining)]
        public void WriteUnmanagedStruct<T>(LSerializeContext* context, ref T? value) where T : unmanaged
        {
            if (WriteInstId(context, ref value, out var instId))
            {
                HandleUnmanagedStruct(out RefHeader* header);
                int size = sizeof(T?);
                context->IncreaseDepth();
                // void* ptr = Unsafe.AsPointer(ref value);
                fixed (void* ptr = &value) WriteUnmanagedValue((byte*)ptr, size);
                context->DecreaseDepth();
                header->InstId = (int)instId;
                header->Size = size;
            }
        }

        [MethodImpl(MethodImplOptions.AggressiveInlining)]
        public void WriteManagedStruct<T>(LSerializeContext* context, ref T value) where T : IEquatable<T>
        {
            if (WriteInstId(context, ref value, out var instId))
            {
                HandleUnmanagedStruct(out RefHeader* header);
                var index = buffer->Index;
                context->IncreaseDepth();
                // GetFormatter<T>().Serialize(context, Ptr, ref value);
                WriteRealTypeValue(context, ref value);
                context->DecreaseDepth();
                header->InstId = (int)instId;
                header->Size = buffer->Index - index;
            } 
        }
        
        [MethodImpl(MethodImplOptions.AggressiveInlining)] 
        public void WriteStruct<T>(LSerializeContext* context, ref T value) where T : IEquatable<T>
        {
            bool isUnmanaged = !RuntimeHelpers.IsReferenceOrContainsReferences<T>();
            if (isUnmanaged)
                WriteUnmanagedStruct(context, ref value);
            else
                WriteManagedStruct(context, ref value);
        }
        
        [MethodImpl(MethodImplOptions.AggressiveInlining)]
        public void WriteReference<T>(LSerializeContext* context, ref T value)
        {
            if (WriteInstIdReference(context, ref value, out var instId))
            {
                HandleUnmanagedStruct(out RefHeader* header);
                var index = buffer->Index;
                context->IncreaseDepth();
                // GetFormatter<T>().Serialize(context, Ptr, ref value);
                WriteRealTypeValue(context, ref value);
                context->DecreaseDepth();
                header->InstId = (int)instId;
                header->Size = buffer->Index - index;
            } 
        }
        
        [MethodImpl(MethodImplOptions.AggressiveInlining)] 
        public void WriteRealTypeValue<T>(LSerializeContext* context, ref T value)
        {
            // Assert.Check(null == value, "null == value");
            var type = value.GetType();
            GetFormatter<Type>().Serialize(context, Ptr, ref type);
            if (type.IsSubclassOf(typeof(T)) || typeof(T).IsAssignableFrom(type))
            {
                object obj = value;
                GetFormatter(type).Serialize(context, Ptr, ref obj); 
            }
            else
            {
                GetFormatter<T>().Serialize(context, Ptr, ref value);
            }
        }
    }
}
